package edu.gatech.cs3240.NFA;

import java.util.ArrayList;
import java.util.Hashtable;
import edu.gatech.cs3240.Token;

/**
 * The NFA stores a set of states (and, implicitly, their associated 
 * transitions).
 * @author VRamachandran
 */
public class NFA {
	
	public static class InvalidNFATraversalException extends Exception {
		private static final long serialVersionUID = 1L;

		public InvalidNFATraversalException(String c) {
			super("Application attempted to traverse a transition which " +
					"consumes a '" + c + "' " +
					"that is not available from the scanner.");
		}
	}

	/**
	 * Outputs a text-basde graphical representation of the NFA to System.out.
	 * @param allStates List of all of the interrelated states to be displayed.
	 */
	public static void output (ArrayList<State> allStates){
		int i, j, i2;
		ArrayList<Transition> curPaths;
		
			//Traverse all states and create the top row of the table.
		for (i = 0; i < allStates.size(); i++)
			System.out.print("       " + allStates.get(i).getName());
		
			//traverse states
		for(i = 0; i < allStates.size(); i++){
			
				//start a new line for every state
			System.out.print('\n');
			
				//get the paths of the current state being traversed.
			curPaths = allStates.get(i).getPaths();
			
				//this puts the name of the state on the current row
			System.out.println(allStates.get(i).getName() + "       ");
			
				//Traverse all of the states again(representing the columns of the table)
			for(i2 = 0; i2 <allStates.size(); i2++){
				
					//traverse all the paths of the current row (state)
				for(j = 0; j < curPaths.size(); j++){
					
						/*if there is a transition in the current row's state that cooresponds to the
						 * current column being traversed then label it with the consumed item.
						 * other wise just go to the next column.
						 */
					if (curPaths.get(j).getTarget().getName() == allStates.get(i2).getName()){
						System.out.print(allStates.get(i).getPaths().get(j).getConsumable() + "      ");
					} else {
						System.out.print("       ");
					}
						
				}
			}
		}
	}

	private ArrayList<State> states;
	private State startState;
	private State acceptState;
	
	public NFA() {
		this(new State(true));
	}
	
	public NFA(State s) {
		states = new ArrayList<State>();
//		System.err.println("Calling NFA's constructor with state " + s.toString());
		states.add(s);
		startState = s;
		s.setAccepting(true);
		acceptState = s;
	}
	
	public State getStartState() {
		return startState;
	}
	
	public State getAcceptState() {
		return acceptState;
	}
	
	public ArrayList<State> getAllStates() {
		return states;
	}
	
	/**
	 * Sets the given state as this NFA's accept state.  Unlike theoretical 
	 * NFAs, our implementation only allows a single accept state, for the sake
	 * of simplicity.  Epsilon transitions are used to link together the accept
	 * states of inner primitive NFAs.
	 * @param parent The NFA that contains the state s (Required to ensure the
	 * integrity of the NFA; parent must contain both s and this NFA).
	 * @param s The state to be set as the accept state for this NFA.
	 * @throws IllegalNFAOperationException If the state s is not a member of
	 * this NFA.
	 */
	public void setAccept(NFA parent, State s) throws IllegalNFAOperationException {
		if(!states.contains(s)) {
			if((!parent.getAllStates().contains(s)) || (!parent.getAllStates().contains(startState)))
				throw new IllegalNFAOperationException("Attempting to set a " +
					"foreign state as accept state.");
		}
		acceptState.setAccepting(false);
		acceptState = s;
		if(!s.isAccepting())
			s.setAccepting(true);
	}
	
	public void addTransition(State source, State destination, Token consume) 
		throws IllegalNFAOperationException {
		if(!states.contains(source))
			throw new IllegalNFAOperationException("Attempting to add a " +
					"transition to a foreign source state.");
		source.addTransition(destination, consume);
		if(!states.contains(destination)) {
			addAllStates(destination);
		}
	}
	
	public void addTransition(State source, State destination) 
		throws IllegalNFAOperationException {
		if(!states.contains(source))
			throw new IllegalNFAOperationException("Attempting to add a " +
					"transition to a foreign source state.");
		source.addTransition(destination);
		if(!states.contains(destination)) {
			addAllStates(destination);
		}
	}
	
	public void addAllStates(State s) {
		states.add(s);
		for(int i = 0; i < s.getPaths().size(); i++) {
			if(!states.contains(s.getPaths().get(i).getTarget()))
				addAllStates(s.getPaths().get(i).getTarget());
		}
	}
	
	/**
	 * Any of a number of problems that may occur when attempting to construct 
	 * NFAs.  Refer to the specific exception message output for details.
	 * @author VRamachandran
	 */
	public static class IllegalNFAOperationException extends Exception {
		private static final long serialVersionUID = 2864904204898442584L;

		public IllegalNFAOperationException(String s) {
			super(s);
			printStackTrace();
		}
	}
	
	/**
	 * Constructs a graphical representation of this NFA, including all states
	 * and transitions between them.
	 */
	public String toString() {
		String ret = "";
		Hashtable<State, Integer> stringized = new Hashtable<State, Integer>();
		ret += startState.toString("", stringized);
		return ret;
	}
}

/*	public boolean checkString(Scanner scanner) {
		// start in the start state
		State curState = this.startState;
		if(scanner.showCurrent().getValue() == '$'){
			if(curState.isAccepting() == true)
				return true;
			else
				return false;
		} else 
			return false;
		}
	}
*/
